{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "aed37924",
   "metadata": {},
   "source": [
    "## 1. 环境检查"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "e217e1d2",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "c:\\ProgramData\\miniconda3\\Lib\\site-packages\\tqdm\\auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n",
      "  from .autonotebook import tqdm as notebook_tqdm\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Python :  3 . 1 3 . 5   |   p a c k a g e d   b y   A n a c o n d a ,   I n c .   |   ( m a i n ,   J u n   1 2   2 0 2 5 ,   1 6 : 3 7 : 0 3 )   [ M S C   v . 1 9 2 9   6 4   b i t   ( A M D 6 4 ) ] \n",
      "OS     : Windows-11-10.0.26100-SP0\n",
      "torch  : 2.9.0+cu130\n",
      "CUDA   : True\n",
      "GPU    : NVIDIA GeForce RTX 5070 Ti\n"
     ]
    }
   ],
   "source": [
    "import os, sys, platform, time, json, re\n",
    "import torch\n",
    "from transformers import AutoTokenizer, AutoModelForCausalLM, TextStreamer\n",
    "\n",
    "print('Python :', sys.version.replace('',' '))\n",
    "print('OS     :', platform.platform())\n",
    "print('torch  :', torch.__version__)\n",
    "print('CUDA   :', torch.cuda.is_available())\n",
    "if torch.cuda.is_available():\n",
    "    print('GPU    :', torch.cuda.get_device_name(0))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f3588193",
   "metadata": {},
   "source": [
    "## 2. 路径配置"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "7f7e912f",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Local model path: E:\\huggingface_models\\qwen\\Qwen3-1.7B\n"
     ]
    }
   ],
   "source": [
    "from pathlib import Path\n",
    "\n",
    "LOCAL_PATH = Path(os.getenv('QWEN_LOCAL_PATH', 'E:/huggingface_models/qwen/Qwen3-1.7B')).resolve()\n",
    "print('Local model path:', LOCAL_PATH)\n",
    "assert LOCAL_PATH.exists(), f'Model path not found: {LOCAL_PATH}'"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3fd8d9b8",
   "metadata": {},
   "source": [
    "## 3. 加载模型与分词器"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "42033667",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Using device: cuda\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Loading checkpoint shards: 100%|██████████| 2/2 [00:00<00:00,  2.28it/s]\n",
      "\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Loaded tokenizer and model from local path.\n"
     ]
    }
   ],
   "source": [
    "device = 'cuda' if torch.cuda.is_available() else 'cpu'\n",
    "print('Using device:', device)\n",
    "\n",
    "# 某些 Qwen 版本需要 trust_remote_code 才能正确加载\n",
    "tokenizer = AutoTokenizer.from_pretrained(str(LOCAL_PATH), use_fast=True, trust_remote_code=True)\n",
    "model = AutoModelForCausalLM.from_pretrained(str(LOCAL_PATH), trust_remote_code=True)\n",
    "model.to(device).eval()\n",
    "\n",
    "# 处理 pad_token_id 以避免警告\n",
    "if tokenizer.pad_token_id is None and tokenizer.eos_token_id is not None:\n",
    "    tokenizer.pad_token_id = tokenizer.eos_token_id\n",
    "\n",
    "print('Loaded tokenizer and model from local path.')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "45f31e77",
   "metadata": {},
   "source": [
    "## 4. 基础生成"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "1e2c217c",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Prompt built. Generating...\n",
      "<think>\n",
      "好的，用户让我用三句话介绍Qwen3-1.7B。首先，我需要确认这个模型的具体信息。Qwen3-1.7B应该是指通义千问3的某个版本，可能在参数量上是1.7B，也就是17亿参数。不过，我需要确保这些信息的准确性，因为有时候可能会有版本更新或者参数变化。\n",
      "\n",
      "接下来，我需要考虑用户的需求。他们可能是在做技术选型，或者想了解这个模型的特点。用户可能想知道它的性能、应用场景或者与其他模型的区别。因此，我需要涵盖关键点：参数量、性能、应用场景，以及可能的优缺点。\n",
      "\n",
      "然后，我需要确保三句话结构清晰，每句话重点明确。第一句介绍模型的基本信息，比如参数量和版本。第二句讲性能，比如在语言理解、生成、推理等方面的能力。第三句可以提到应用场景，比如自然语言处理任务，以及可能的其他特性，比如多语言支持或高效推理。\n",
      "\n",
      "还要注意避免技术术语过多，保持简洁易懂。同时，检查是否有最新的信息需要更新，比如是否有新功能或改进。如果不确定某些细节，可能需要说明是基于现有信息，或者建议用户查阅\n",
      "<think>\n",
      "好的，用户让我用三句话介绍Qwen3-1.7B。首先，我需要确认这个模型的具体信息。Qwen3-1.7B应该是指通义千问3的某个版本，可能在参数量上是1.7B，也就是17亿参数。不过，我需要确保这些信息的准确性，因为有时候可能会有版本更新或者参数变化。\n",
      "\n",
      "接下来，我需要考虑用户的需求。他们可能是在做技术选型，或者想了解这个模型的特点。用户可能想知道它的性能、应用场景或者与其他模型的区别。因此，我需要涵盖关键点：参数量、性能、应用场景，以及可能的优缺点。\n",
      "\n",
      "然后，我需要确保三句话结构清晰，每句话重点明确。第一句介绍模型的基本信息，比如参数量和版本。第二句讲性能，比如在语言理解、生成、推理等方面的能力。第三句可以提到应用场景，比如自然语言处理任务，以及可能的其他特性，比如多语言支持或高效推理。\n",
      "\n",
      "还要注意避免技术术语过多，保持简洁易懂。同时，检查是否有最新的信息需要更新，比如是否有新功能或改进。如果不确定某些细节，可能需要说明是基于现有信息，或者建议用户查阅\n"
     ]
    }
   ],
   "source": [
    "def build_prompt(messages):\n",
    "    # 优先使用 chat template\n",
    "    if hasattr(tokenizer, 'apply_chat_template'):\n",
    "        return tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)\n",
    "    # 退化为简单拼接\n",
    "    parts = []\n",
    "    for m in messages:\n",
    "        role = m.get('role','user')\n",
    "        content = m.get('content','')\n",
    "        parts.append(f\"{role}: {content}\")\n",
    "    parts.append('assistant:')\n",
    "    return \"\\n\".join(parts)\n",
    "\n",
    "def generate_once(prompt, max_new_tokens=256, temperature=0.7, top_p=0.9):\n",
    "    inputs = tokenizer(prompt, return_tensors='pt').to(device)\n",
    "    with torch.no_grad():\n",
    "        output_ids = model.generate(**inputs,\n",
    "                                   max_new_tokens=max_new_tokens,\n",
    "                                   do_sample=(temperature > 0),\n",
    "                                   temperature=temperature,\n",
    "                                   top_p=top_p,\n",
    "                                   pad_token_id=tokenizer.pad_token_id)\n",
    "    gen_ids = output_ids[0][inputs['input_ids'].shape[1]:]\n",
    "    return tokenizer.decode(gen_ids, skip_special_tokens=True).strip()\n",
    "\n",
    "messages = [\n",
    "    { 'role': 'system', 'content': 'You are a helpful assistant.' },\n",
    "    { 'role': 'user',   'content': '用三句话介绍一下Qwen3-1.7B。' }\n",
    " ]\n",
    "prompt = build_prompt(messages)\n",
    "print('Prompt built. Generating...')\n",
    "text = generate_once(prompt)\n",
    "print(text)\n",
    "\n",
    "# 保存本次生成结果供后续信息抽取使用\n",
    "last_generated_text = text"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "06fb14c4",
   "metadata": {},
   "source": [
    "## 5. 多轮对话（保持上下文）"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "d7f11757",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Assistant: <think>\n",
      "嗯，用户让我用一句话描述我的能力。首先，我需要确定用户的需求是什么。他们可能是在了解我的功能，或者想确认我是否适合某个任务。用户可能是想测试我的能力，或者需要我帮忙完成某个项目。\n",
      "\n",
      "接下来，我要考虑如何简洁明了地表达我的能力。我应该涵盖主要功能，比如语言理解、生成、对话、多语言支持等。同时，要突出我的优势，比如准确性和互动性。\n",
      "\n",
      "还要注意用户可能的深层需求。他们可能希望我能够处理复杂的任务，或者在特定场景下发挥作用。比如，他们可能需要我帮助写文章、进行翻译，或者提供信息。因此，我的描述需要足够通用，以适应各种应用场景。\n",
      "\n",
      "另外，用户可能希望我具备一定的灵活性，能够适应不同的任务类型。所以，在描述中要提到我的多用途能力，以及如何根据不同需求调整工作方式。\n",
      "\n",
      "最后，确保句子结构清晰，没有冗余，同时保持专业和友好。可能需要多次调整，确保信息准确且吸引人。\n",
      "</think>\n",
      "\n",
      "我具备强大的自然语言处理能力，能够理解、生成多种语言文本，支持对话交流、信息查询、创意写作等任务，并能根据需求灵活调整工作方式以满足不同场景\n",
      "Assistant: <think>\n",
      "好的，用户之前让我用一句话描述我的能力，现在他们希望更技术性的表述。首先，我需要回顾之前的回答，确保这次更深入。用户可能是在准备技术文档或者需要更专业的描述，比如用于简历或项目说明。\n",
      "\n",
      "接下来，我要考虑技术术语的使用。比如，自然语言处理（NLP）相关的技术，如序列模型、Transformer架构、注意力机制等。还要提到具体的模型，比如BERT、GPT，或者预训练语言模型，这样显得更专业。\n",
      "\n",
      "然后，用户可能希望强调我的功能，比如文本生成、对话理解、多语言支持、上下文理解、推理能力等。需要将这些功能用技术术语表达，比如“基于Transformer的深度学习架构”、“多语言支持”、“上下文感知的对话理解”等。\n",
      "\n",
      "还要注意用户可能的深层需求，比如他们可能需要这些信息用于学术研究、产品开发，或者展示技术优势。因此，回答需要准确且全面，涵盖主要技术点，同时保持简洁。\n",
      "\n",
      "最后，检查是否符合技术文档的规范，使用专业术语，结构清晰，避免冗余。确保每个技术点都有对应的功能描述，让用户明白我的能力如何通过这些技术实现。\n",
      "</think>\n",
      "\n",
      "我基于Transformer\n",
      "Assistant: <think>\n",
      "好的，用户之前让我用一句话描述我的能力，现在他们希望更技术性的表述。首先，我需要回顾之前的回答，确保这次更深入。用户可能是在准备技术文档或者需要更专业的描述，比如用于简历或项目说明。\n",
      "\n",
      "接下来，我要考虑技术术语的使用。比如，自然语言处理（NLP）相关的技术，如序列模型、Transformer架构、注意力机制等。还要提到具体的模型，比如BERT、GPT，或者预训练语言模型，这样显得更专业。\n",
      "\n",
      "然后，用户可能希望强调我的功能，比如文本生成、对话理解、多语言支持、上下文理解、推理能力等。需要将这些功能用技术术语表达，比如“基于Transformer的深度学习架构”、“多语言支持”、“上下文感知的对话理解”等。\n",
      "\n",
      "还要注意用户可能的深层需求，比如他们可能需要这些信息用于学术研究、产品开发，或者展示技术优势。因此，回答需要准确且全面，涵盖主要技术点，同时保持简洁。\n",
      "\n",
      "最后，检查是否符合技术文档的规范，使用专业术语，结构清晰，避免冗余。确保每个技术点都有对应的功能描述，让用户明白我的能力如何通过这些技术实现。\n",
      "</think>\n",
      "\n",
      "我基于Transformer\n",
      "Assistant: <think>\n",
      "Okay, the user wants me to shorten the previous response to 30 characters. Let me check the original response again. The original was: \"我具备强大的自然语言处理能力，能够理解、生成多种语言文本，支持对话交流、信息查询、创意写作等任务，并能根据需求灵活调整工作方式以满足不同场景.\" \n",
      "\n",
      "First, I need to count the characters. Let me see: \"我具备强大的自然语言处理能力，能够理解、生成多种语言文本，支持对话交流、信息查询、创意写作等任务，并能根据需求灵活调整工作方式以满足不同场景.\" That's a bit long. \n",
      "\n",
      "The user wants it to be concise. Maybe I can remove some words. \"基于Transformer\" is a key part. So, \"基于Transformer，具备多语言处理与任务适配能力。\" That's 30 characters. Let me count: \"基于Transformer，具备多语言处理与任务适配能力。\" That's exactly 30 characters. \n",
      "\n",
      "I should make sure there are no typos and that it's accurate. The key points are using Transformer, multilingual processing, and task adaptation. Yes, that covers it. The user might be looking for a technical description for\n",
      "Assistant: <think>\n",
      "Okay, the user wants me to shorten the previous response to 30 characters. Let me check the original response again. The original was: \"我具备强大的自然语言处理能力，能够理解、生成多种语言文本，支持对话交流、信息查询、创意写作等任务，并能根据需求灵活调整工作方式以满足不同场景.\" \n",
      "\n",
      "First, I need to count the characters. Let me see: \"我具备强大的自然语言处理能力，能够理解、生成多种语言文本，支持对话交流、信息查询、创意写作等任务，并能根据需求灵活调整工作方式以满足不同场景.\" That's a bit long. \n",
      "\n",
      "The user wants it to be concise. Maybe I can remove some words. \"基于Transformer\" is a key part. So, \"基于Transformer，具备多语言处理与任务适配能力。\" That's 30 characters. Let me count: \"基于Transformer，具备多语言处理与任务适配能力。\" That's exactly 30 characters. \n",
      "\n",
      "I should make sure there are no typos and that it's accurate. The key points are using Transformer, multilingual processing, and task adaptation. Yes, that covers it. The user might be looking for a technical description for\n"
     ]
    }
   ],
   "source": [
    "def chat(messages, user_text, **gen_kwargs):\n",
    "    messages = list(messages) + [{ 'role': 'user', 'content': user_text }]\n",
    "    prompt = build_prompt(messages)\n",
    "    reply = generate_once(prompt, **gen_kwargs)\n",
    "    messages.append({ 'role': 'assistant', 'content': reply })\n",
    "    return messages, reply\n",
    "\n",
    "history = [\n",
    "    { 'role': 'system', 'content': 'You are a helpful assistant.' }\n",
    "]\n",
    "history, r1 = chat(history, '你好！用一句话描述你的能力。')\n",
    "print('Assistant:', r1)\n",
    "history, r2 = chat(history, '继续，用更技术一些的表述。', temperature=0.6)\n",
    "print('Assistant:', r2)\n",
    "history, r3 = chat(history, '把上一条回答精简到30个字以内。', temperature=0.6)\n",
    "print('Assistant:', r3)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e757b16c",
   "metadata": {},
   "source": [
    "## 6. 流式输出（TextStreamer）"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "5259b875",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Streaming response:\n",
      "<think>\n",
      "<think>\n",
      "好的好的，用户，用户让我让我给出给出三个三个提升提升Python代码Python代码性能性能的小的小技巧技巧。首先。首先，我，我需要需要回想回想一下一下Python性能Python性能优化优化的的常见常见方法方法。可能。可能的的方面方面包括包括减少减少不必要的不必要的计算计算、使用、使用高效高效的数据的数据结构结构、避免、避免全局全局变量变量等等等等。\n",
      "\n",
      "。\n",
      "\n",
      "第一个第一个想到想到的是的是使用使用生成生成器器和和迭代迭代器器。因为。因为Python的Python的生成生成器器可以可以避免避免不必要的不必要的内存内存占用占用，特别是在，特别是在处理处理大数据大数据时时，这样，这样可以可以提高提高效率效率。比如。比如，用，用生成生成器器表达表达式式代替代替列表列表推推导导式式，或者，或者使用使用迭代迭代器器来来遍遍历历数据数据，这样，这样可以可以减少减少内存内存拷拷贝贝，提升，提升性能性能。\n",
      "\n",
      "。\n",
      "\n",
      "第二个第二个可能是可能是使用使用局部局部变量变量和和避免避免全局全局变量变量。在。在函数函数内部内部使用使用局部局部变量变量而不是而不是全局全局变量变量，可以，可以减少减少访问访问全局全局变量变量的的开开销销，提高，提高执行执行速度速度。此外。此外，使用，使用闭闭包包或者或者装饰装饰器器来来管理管理变量变量也可能也可能有有帮助帮助。\n",
      "\n",
      "。\n",
      "\n",
      "第三个第三个技巧技巧可能可能涉及涉及使用使用C扩展C扩展，比如，比如用用Cython或者Cython或者用用NumPy。不过NumPy。不过用户用户可能可能需要需要更更基础基础的的技巧技巧，所以，所以可能可能需要需要考虑考虑其他其他方法方法。比如。比如，使用，使用内置内置函数函数和和库库，比如，比如用用map、filter代替map、filter代替显显式式循环循环，或者，或者使用使用更更高效高效的数据的数据结构结构如如集合集合而不是而不是列表列表。\n",
      "\n",
      "。\n",
      "\n",
      "另外另外，可能，可能还要还要考虑考虑避免避免不必要的不必要的类型类型转换转换，比如，比如在在循环循环中中直接直接操作操作数据数据而不是而不是转换转换类型类型。还有。还有，使用，使用局部局部变量变量和和避免避免重复重复计算计算，比如，比如在在函数函数内部内部预先预先计算\n",
      "计算\n"
     ]
    }
   ],
   "source": [
    "def stream_once(prompt, max_new_tokens=256, temperature=0.7):\n",
    "    inputs = tokenizer(prompt, return_tensors='pt').to(device)\n",
    "    streamer = TextStreamer(tokenizer, skip_prompt=True, skip_special_tokens=True)\n",
    "    with torch.no_grad():\n",
    "        _ = model.generate(**inputs,\n",
    "                          max_new_tokens=max_new_tokens,\n",
    "                          do_sample=(temperature > 0),\n",
    "                          temperature=temperature,\n",
    "                          streamer=streamer,\n",
    "                          pad_token_id=tokenizer.pad_token_id)\n",
    "\n",
    "print('Streaming response:')\n",
    "messages = [\n",
    "    { 'role': 'system', 'content': 'You are a helpful assistant.' },\n",
    "    { 'role': 'user',   'content': '请逐条输出三个提升Python代码性能的小技巧。' }\n",
    "]\n",
    "prompt = build_prompt(messages)\n",
    "stream_once(prompt)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e02558dc",
   "metadata": {},
   "source": [
    "## 7. 结构化 JSON 输出（简单解析）"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "66091d4e",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "The following generation flags are not valid and may be ignored: ['temperature', 'top_k']. Set `TRANSFORMERS_VERBOSITY=info` for more details.\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Raw output:\n",
      " <think>\n",
      "好的，用户让我按照特定的JSON Schema输出三点使用Transformers的建议。首先，我需要确认用户的需求。他们可能是在开发一个应用，需要指导如何有效使用Transformers库，比如Hugging Face的Transformers库。用户可能希望得到清晰、简洁的建议，适用于不同场景。\n",
      "\n",
      "接下来，我得确保JSON结构正确。Schema要求标题为空字符串，三个要点用数组存储。需要检查每个要点是否符合要求，没有多余的内容。用户可能希望这三点有实际价值，比如数据预处理、模型选择、训练优化等。\n",
      "\n",
      "然后，考虑用户可能的深层需求。他们可能希望这些建议能够帮助开发者避免常见错误，提高效率，或者适应不同的任务类型。因此，建议需要涵盖不同的方面，如数据处理、模型调优、性能优化等。\n",
      "\n",
      "最后，确保输出严格符合格式，没有多余的信息，只返回JSON。需要验证每个字段是否正确，特别是标题和bullets数组的元素是否都是字符串，并且没有使用任何markdown或额外文本。\n",
      "</think>\n",
      "\n",
      "{\"title\": \"\", \"bullets\": [\"进行数据预处理时确保输入格式与模型要求一致\", \"选择适合任务的预训练模型并调整参数以优化性能\", \"\n",
      "\n",
      "Retry raw output:\n",
      " <think>\n",
      "好的，用户让我按照给定的Schema输出三点使用Transformers的建议。首先，我需要确认Schema的结构，标题是空字符串，三个项目符号项，每个项都是空字符串。不过用户可能希望标题有内容，但根据Schema，标题是空的，所以直接留空。\n",
      "\n",
      "接下来，我需要确保三点建议是有效的，符合实际使用Transformers时的注意事项。比如，确保模型规模合适，数据预处理正确，以及定期训练和评估模型性能。这三个点应该覆盖了常见的最佳实践，同时保持简洁。\n",
      "\n",
      "然后，检查是否每个bullet点都是空字符串，没有多余的内容。用户可能希望每个点都是独立的，所以用三个空字符串。最后，确保整个JSON结构正确，没有语法错误，比如引号和逗号的使用。确认没有添加额外的信息，只输出符合Schema的JSON。\n",
      "</think>\n",
      "\n",
      "{\"title\": \"\", \"bullets\": [\"\", \"\", \"\"]}\n",
      "\n",
      "Parsed JSON: {'title': '', 'bullets': ['', '', '']}\n",
      "\n",
      "Retry raw output:\n",
      " <think>\n",
      "好的，用户让我按照给定的Schema输出三点使用Transformers的建议。首先，我需要确认Schema的结构，标题是空字符串，三个项目符号项，每个项都是空字符串。不过用户可能希望标题有内容，但根据Schema，标题是空的，所以直接留空。\n",
      "\n",
      "接下来，我需要确保三点建议是有效的，符合实际使用Transformers时的注意事项。比如，确保模型规模合适，数据预处理正确，以及定期训练和评估模型性能。这三个点应该覆盖了常见的最佳实践，同时保持简洁。\n",
      "\n",
      "然后，检查是否每个bullet点都是空字符串，没有多余的内容。用户可能希望每个点都是独立的，所以用三个空字符串。最后，确保整个JSON结构正确，没有语法错误，比如引号和逗号的使用。确认没有添加额外的信息，只输出符合Schema的JSON。\n",
      "</think>\n",
      "\n",
      "{\"title\": \"\", \"bullets\": [\"\", \"\", \"\"]}\n",
      "\n",
      "Parsed JSON: {'title': '', 'bullets': ['', '', '']}\n"
     ]
    }
   ],
   "source": [
    "import json, re\n",
    "from typing import Any\n",
    "\n",
    "def extract_json_block(text: str) -> Any:\n",
    "    # 优先解析 ```json ... ``` 代码块\n",
    "    m = re.search(r\"```json\\s*([\\s\\S]*?)\\s*```\", text, re.IGNORECASE)\n",
    "    if m:\n",
    "        cand = m.group(1).strip()\n",
    "        try:\n",
    "            return json.loads(cand)\n",
    "        except Exception:\n",
    "            pass\n",
    "    # 尝试 { ... } 或 [ ... ] 包裹的 JSON 片段\n",
    "    for open_ch, close_ch in ((\"{\",\"}\"), (\"[\", \"]\")):\n",
    "        i, j = text.find(open_ch), text.rfind(close_ch)\n",
    "        if i != -1 and j != -1 and j > i:\n",
    "            cand = text[i:j+1]\n",
    "            try:\n",
    "                return json.loads(cand)\n",
    "            except Exception:\n",
    "                pass\n",
    "    return None\n",
    "\n",
    "# 使用一个有效的 Schema 提示，帮助模型按结构输出\n",
    "schema_hint = '{\"title\": \"\", \"bullets\": [\"\", \"\", \"\"]}'\n",
    "\n",
    "messages = [\n",
    "  { 'role': 'system', 'content': ('Return ONLY a valid JSON matching the given schema. '\n",
    "                                   'No extra text, no markdown, no comments, double quotes only.') },\n",
    "  { 'role': 'user',   'content': f'按照如下Schema输出：{schema_hint}。主题：列出三点使用Transformers的建议。'}\n",
    "]\n",
    "prompt = build_prompt(messages)\n",
    "raw = generate_once(prompt, temperature=0.2)\n",
    "print('Raw output:\\n', raw)\n",
    "\n",
    "obj = extract_json_block(raw)\n",
    "if obj is None:\n",
    "    # 进行一次严格重试：要求仅返回JSON且不使用Markdown代码块\n",
    "    messages[0]['content'] += ' Respond with JSON only, no markdown fences.'\n",
    "    prompt = build_prompt(messages)\n",
    "    raw = generate_once(prompt, temperature=0.0, top_p=1.0)\n",
    "    print('\\nRetry raw output:\\n', raw)\n",
    "    obj = extract_json_block(raw) or {}\n",
    "\n",
    "print('\\nParsed JSON:', obj)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "930baf2d",
   "metadata": {},
   "source": [
    "## 8. 性能与内存建议\n",
    "- 若显存不足，可考虑：\n",
    "  - 使用 `bitsandbytes` 进行 8/4bit 量化（需要额外依赖与兼容GPU）。\n",
    "  - 将 `max_new_tokens` 降低，或调小 batch。\n",
    "  - CPU 推理：性能较慢，建议仅用于功能验证。\n",
    "- 若有 `accelerate`，可在 `from_pretrained` 中使用 `device_map='auto'` 与 `torch_dtype=torch.float16`（兼容视模型而定）。\n",
    "- 首次运行会构建权重到设备，第二次开始通常更快。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "aea7a0b0",
   "metadata": {},
   "source": [
    "## 9. 基于 Pydantic 的信息抽取示例\n",
    "本示例演示如何让本地 模型 输出严格的 JSON，并用 Pydantic 进行结构化校验与消费。\n",
    "我们将从一段“发票文本”中抽取字段，包括抬头、发票号、日期、总额、币种以及明细项。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "802f2a45",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Raw output:\n",
      " <think>\n",
      "好的，我现在需要处理用户的请求，抽取发票信息并按照给定的Schema返回。首先，我要仔细阅读用户提供的文本，确保不遗漏任何细节。\n",
      "\n",
      "用户给出的文本中有几个部分需要提取：供应商名称、发票号、开票日期、币种，以及明细和合计金额。还有备注部分需要注意，可能影响单价计算。\n",
      "\n",
      "首先，供应商是“杭州某科技有限公司”，所以vendor_name应该是这个。发票号是HZ-2025-001，直接提取。开票日期是2025-11-01，正确无误。币种是CNY，没问题。\n",
      "\n",
      "接下来是明细部分。用户列出了两个项目：高性能服务器租用和数据存储服务。第一个项目是10台，单价3999.00元，所以quantity是10，unit_price是3999.00，amount就是10*3999=39990元。第二个项目是20TB，单价199.50元，quantity是20，unit_price是199.50，amount是20*199.50=3990元。合计是43990.00元，这应该是两个项目金额相加的结果，39990+3990=43980，但用户给出的合计是43990，可能需要检查是否有计算错误。不过根据用户提供的文本，合计是43990，所以直接使用即可。\n",
      "\n",
      "备注部分提到本月优惠已包含在单价中，但可能不影响金额计算，所以不需要处理。\n",
      "\n",
      "现在需要按照Schema填写。注意金额要以数字形式，不带单位。line_items中的每个项目需要有description、quantity、unit_price和amount。这里description可能需要更准确的描述，比如“高性能服务器租用”和“数据存储服务”，但用户可能希望更简洁，所以直接使用原文中的描述。\n",
      "\n",
      "检查是否所有字段都正确填充，特别是金额是否正确计算。例如，第一个项目10台*3999=39990，第二个20*199.5=3990，合计43980，但用户给出的合计是43990，可能有笔误，但按照用户提供的文本，直接使用即可。\n",
      "\n",
      "最后，确保没有多余的信息，只返回JSON，不包含其他内容。检查是否所有字段都符合要求，特别是total_amount是合计金额，即43990，currency是CNY。确认无误后，生成对应的JSON结构。\n",
      "</think>\n",
      "\n",
      "{\n",
      "  \"vendor_name\": \"杭州某科技有限公司\",\n",
      "  \"invoice_number\": \"HZ-2025-001\",\n",
      "  \"date\": \"2025-11-01\",\n",
      "  \"total_amount\": 43990.0,\n",
      "  \"currency\": \"CNY\",\n",
      "  \"line_items\": [\n",
      "    {\n",
      "      \"description\": \"高性能服务器租用\",\n",
      "      \"quantity\": 10,\n",
      "      \"unit_price\": 3999.0,\n",
      "      \"amount\": 39990.0\n",
      "    },\n",
      "    {\n",
      "      \"description\": \"数据存储服务\",\n",
      "      \"quantity\": 20,\n",
      "      \"unit_price\": 199.5,\n",
      "      \"amount\": 3990.0\n",
      "    }\n",
      "  ]\n",
      "}\n",
      "obj(JSON parsed)= {\"vendor_name\": \"杭州某科技有限公司\", \"invoice_number\": \"HZ-2025-001\", \"date\": \"2025-11-01\", \"total_amount\": 43990.0, \"currency\": \"CNY\", \"line_items\": [{\"description\": \"高性能服务器租用\", \"quantity\": 10, \"unit_price\": 3999.0, \"amount\": 39990.0}, {\"description\": \"数据存储服务\", \"quantity\": 20, \"unit_price\": 199.5, \"amount\": 3990.0}]}\n"
     ]
    }
   ],
   "source": [
    "from typing import List, Optional, Any\n",
    "from pydantic import BaseModel, Field\n",
    "import json, re, math\n",
    "\n",
    "# 依赖：本节使用了前文定义的 build_prompt、generate_once、extract_json_block。\n",
    "try:\n",
    "    build_prompt\n",
    "    generate_once\n",
    "    extract_json_block\n",
    "except NameError:\n",
    "    raise RuntimeError('请先运行前面的单轮生成单元（第4节）以定义所需辅助函数。')\n",
    "\n",
    "class LineItem(BaseModel):\n",
    "    description: str = Field(description='项目描述')\n",
    "    quantity: float = Field(description='数量')\n",
    "    unit_price: float = Field(description='单价')\n",
    "    amount: float = Field(description='金额（quantity*unit_price）')\n",
    "\n",
    "class Invoice(BaseModel):\n",
    "    vendor_name: str = Field(description='供应商或抬头')\n",
    "    invoice_number: str = Field(description='发票号')\n",
    "    date: str = Field(description='日期，ISO或自然语言均可')\n",
    "    total_amount: float = Field(description='发票总额')\n",
    "    currency: str = Field(description='币种，如 CNY/USD/EUR')\n",
    "    line_items: List[LineItem] = Field(default_factory=list, description='明细项')\n",
    "\n",
    "# 若存在上一步生成结果，则以其为抽取来源，否则回退到示例文本(仅用于让模型看到原始票据内容)\n",
    "sample_text = \"\"\"\n",
    "供应商：杭州某科技有限公司\n",
    "发票号：HZ-2025-001\n",
    "开票日期：2025-11-01\n",
    "币种：CNY\n",
    "明细：\n",
    "1) 高性能服务器租用（10台，单价 3999.00 元）\n",
    "2) 数据存储服务（20TB，单价 199.50 元）\n",
    "合计：43990.00 元\n",
    "备注：本月优惠已包含在单价中。\n",
    "\"\"\"\n",
    "\n",
    "schema_hint = '{\"vendor_name\":\"\",\"invoice_number\":\"\",\"date\":\"\",\"total_amount\":0.0,\"currency\":\"\",\"line_items\":[{\"description\":\"\",\"quantity\":0,\"unit_price\":0.0,\"amount\":0.0}]}'\n",
    "\n",
    "messages = [\n",
    "    { 'role': 'system', 'content': ('你是信息抽取助手。严格按下述 Schema 返回且仅返回一个 JSON。'\n",
    "                                   '不要返回空字符串或0作为占位，除非原文确无信息；数字不带单位；'\n",
    "                                   'line_items 中 amount=quantity*unit_price。') },\n",
    "    { 'role': 'user',   'content': f'根据如下文本抽取发票信息，Schema如下：{schema_hint}\\n\\n文本：\\n{sample_text}' }\n",
    " ]\n",
    "prompt = build_prompt(messages)\n",
    "\n",
    "# 低多样性，高选词范围，以提高结构化输出的准确性\n",
    "raw = generate_once(prompt, temperature=0.1, top_p=0.9, max_new_tokens=1024)\n",
    "print('Raw output:\\n', raw)\n",
    "obj = extract_json_block(raw) or {}\n",
    "print('obj(JSON parsed)= ' + json.dumps(obj, ensure_ascii=False))\n",
    "\n"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "base",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.13.5"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
